<html lang="en-US">

<head>
    
<title>深入理解 Go defer - 花落雨忧</title>

<meta property="og:title" content="深入理解 Go defer - 花落雨忧">



    



    
    <meta property="description" content="在上一章节 《深入理解 Go panic and recover》中，我们发现了 defer 与其关联性极大，还是觉得非常有必要深入一下。希望通过本章节大家可以对 defer 关键字有一个深刻的理解，那么我们开始吧。你先等等，请排好队，我们这儿采取后进先出 LIFO 的出站方式&amp;hellip;
[&amp;hellip;] 我们简单的过一下 defer 关键字的基础使用， &amp;hellip;">
    <meta property="og:description" content="在上一章节 《深入理解 Go panic and recover》中，我们发现了 defer 与其关联性极大，还是觉得非常有必要深入一下。希望通过本章节大家可以对 defer 关键字有一个深刻的理解，那么我们开始吧。你先等等，请排好队，我们这儿采取后进先出 LIFO 的出站方式&amp;hellip;
[&amp;hellip;] 我们简单的过一下 defer 关键字的基础使用， &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.site/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.site/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.site/js/jquery.min.js"></script>




<link href="https://www.lican.site/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>
        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/go/defer/2019-05-27-defer/">深入理解 Go defer</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2019/05/27</a>
                                            
                                            
                                            
                                            By 
                                            <a class="author" href="javascript:;">煎鱼</a></div>
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>在上一章节 《深入理解 Go panic and recover》中，我们发现了 <code>defer</code> 与其关联性极大，还是觉得非常有必要深入一下。希望通过本章节大家可以对 <code>defer</code> 关键字有一个深刻的理解，那么我们开始吧。你先等等，请排好队，我们这儿采取后进先出 LIFO 的出站方式&hellip;</p>
<h2 id="特性">特性</h2>
<p>我们简单的过一下 <code>defer</code> 关键字的基础使用，让大家先有一个基础的认知</p>
<h3 id="一延迟调用">一、延迟调用</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;EDDYCJY.&#34;</span>)

	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;end.&#34;</span>)
}
</code></pre></div><p>输出结果：</p>
<pre><code>$ go run main.go
2019/05/19 21:15:02 end.
2019/05/19 21:15:02 EDDYCJY.
</code></pre><h3 id="二后进先出">二、后进先出</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">6</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;EDDYCJY&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">i</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.&#34;</span>)
	}


	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;end.&#34;</span>)
}
</code></pre></div><p>输出结果：</p>
<pre><code>$ go run main.go
2019/05/19 21:19:17 end.
2019/05/19 21:19:17 EDDYCJY5.
2019/05/19 21:19:17 EDDYCJY4.
2019/05/19 21:19:17 EDDYCJY3.
2019/05/19 21:19:17 EDDYCJY2.
2019/05/19 21:19:17 EDDYCJY1.
2019/05/19 21:19:17 EDDYCJY0.
</code></pre><h3 id="三运行时间点">三、运行时间点</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">func</span>() {
		 <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer.EDDYCJY.&#34;</span>)
	}()

	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main.EDDYCJY.&#34;</span>)
}
</code></pre></div><p>输出结果：</p>
<pre><code>$ go run main.go
2019/05/22 23:30:27 defer.EDDYCJY.
2019/05/22 23:30:27 main.EDDYCJY.
</code></pre><h3 id="四异常处理">四、异常处理</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> recover(); <span style="color:#a6e22e">e</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;EDDYCJY.&#34;</span>)
		}
	}()

	panic(<span style="color:#e6db74">&#34;end.&#34;</span>)
}
</code></pre></div><p>输出结果：</p>
<pre><code>$ go run main.go
2019/05/20 22:22:57 EDDYCJY.
</code></pre><h2 id="源码剖析">源码剖析</h2>
<pre><code>$ go tool compile -S main.go
&quot;&quot;.main STEXT size=163 args=0x0 locals=0x40
	...
	0x0059 00089 (main.go:6)	MOVQ	AX, 16(SP)
	0x005e 00094 (main.go:6)	MOVQ	$1, 24(SP)
	0x0067 00103 (main.go:6)	MOVQ	$1, 32(SP)
	0x0070 00112 (main.go:6)	CALL	runtime.deferproc(SB)
	0x0075 00117 (main.go:6)	TESTL	AX, AX
	0x0077 00119 (main.go:6)	JNE	137
	0x0079 00121 (main.go:7)	XCHGL	AX, AX
	0x007a 00122 (main.go:7)	CALL	runtime.deferreturn(SB)
	0x007f 00127 (main.go:7)	MOVQ	56(SP), BP
	0x0084 00132 (main.go:7)	ADDQ	$64, SP
	0x0088 00136 (main.go:7)	RET
	0x0089 00137 (main.go:6)	XCHGL	AX, AX
	0x008a 00138 (main.go:6)	CALL	runtime.deferreturn(SB)
	0x008f 00143 (main.go:6)	MOVQ	56(SP), BP
	0x0094 00148 (main.go:6)	ADDQ	$64, SP
	0x0098 00152 (main.go:6)	RET
	...
</code></pre><p>首先我们需要找到它，找到它实际对应什么执行代码。通过汇编代码，可得知涉及如下方法：</p>
<ul>
<li>runtime.deferproc</li>
<li>runtime.deferreturn</li>
</ul>
<p>很显然是运行时的方法，是对的人。我们继续往下走看看都分别承担了什么行为</p>
<h3 id="数据结构">数据结构</h3>
<p>在开始前我们需要先介绍一下 <code>defer</code> 的基础单元 <code>_defer</code> 结构体，如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">_defer</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">siz</span>     <span style="color:#66d9ef">int32</span>
	<span style="color:#a6e22e">started</span> <span style="color:#66d9ef">bool</span>
	<span style="color:#a6e22e">sp</span>      <span style="color:#66d9ef">uintptr</span> <span style="color:#75715e">// sp at time of defer
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">pc</span>      <span style="color:#66d9ef">uintptr</span>
	<span style="color:#a6e22e">fn</span>      <span style="color:#f92672">*</span><span style="color:#a6e22e">funcval</span>
	<span style="color:#a6e22e">_panic</span>  <span style="color:#f92672">*</span><span style="color:#a6e22e">_panic</span> <span style="color:#75715e">// panic that is running defer
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">link</span>    <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
}

<span style="color:#f92672">...</span>
<span style="color:#66d9ef">type</span> <span style="color:#a6e22e">funcval</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">fn</span> <span style="color:#66d9ef">uintptr</span>
	<span style="color:#75715e">// variable-size, fn-specific data here
</span><span style="color:#75715e"></span>}
</code></pre></div><ul>
<li>siz：所有传入参数的总大小</li>
<li>started：该 <code>defer</code> 是否已经执行过</li>
<li>sp：函数栈指针寄存器，一般指向当前函数栈的栈顶</li>
<li>pc：程序计数器，有时称为指令指针(IP)，线程利用它来跟踪下一个要执行的指令。在大多数处理器中，PC 指向的是下一条指令，而不是当前指令</li>
<li>fn：指向传入的函数地址和参数</li>
<li>_panic：指向 <code>_panic</code> 链表</li>
<li>link：指向 <code>_defer</code> 链表</li>
</ul>
<p><img src="https://s2.ax1x.com/2020/02/27/3dLNjJ.png" alt="image"></p>
<h3 id="deferproc">deferproc</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferproc</span>(<span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">int32</span>, <span style="color:#a6e22e">fn</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">funcval</span>) {
    <span style="color:#f92672">...</span>
	<span style="color:#a6e22e">sp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallersp</span>()
	<span style="color:#a6e22e">argp</span> <span style="color:#f92672">:=</span> uintptr(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">fn</span>)) <span style="color:#f92672">+</span> <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Sizeof</span>(<span style="color:#a6e22e">fn</span>)
	<span style="color:#a6e22e">callerpc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallerpc</span>()

	<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">newdefer</span>(<span style="color:#a6e22e">siz</span>)
    <span style="color:#f92672">...</span>
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span> = <span style="color:#a6e22e">fn</span>
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">pc</span> = <span style="color:#a6e22e">callerpc</span>
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sp</span> = <span style="color:#a6e22e">sp</span>
	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">siz</span> {
	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
		<span style="color:#75715e">// Do nothing.
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">sys</span>.<span style="color:#a6e22e">PtrSize</span>:
		<span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>)) = <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#a6e22e">argp</span>))
	<span style="color:#66d9ef">default</span>:
		<span style="color:#a6e22e">memmove</span>(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>), <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#a6e22e">argp</span>), uintptr(<span style="color:#a6e22e">siz</span>))
	}

	<span style="color:#a6e22e">return0</span>()
}
</code></pre></div><ul>
<li>获取调用 <code>defer</code> 函数的函数栈指针、传入函数的参数具体地址以及 PC （程序计数器），也就是下一个要执行的指令。这些相当于是预备参数，便于后续的流转控制</li>
<li>创建一个新的 <code>defer</code> 最小单元 <code>_defer</code>，填入先前准备的参数</li>
<li>调用 <code>memmove</code> 将传入的参数存储到新 <code>_defer</code> （当前使用）中去，便于后续的使用</li>
<li>最后调用 <code>return0</code> 进行返回，这个函数非常重要。能够避免在 <code>deferproc</code> 中又因为返回 <code>return</code>，而诱发 <code>deferreturn</code> 方法的调用。其根本原因是一个停止 <code>panic</code> 的延迟方法会使 <code>deferproc</code> 返回 1，但在机制中如果 <code>deferproc</code> 返回不等于 0，将会总是检查返回值并跳转到函数的末尾。而 <code>return0</code> 返回的就是 0，因此可以防止重复调用</li>
</ul>
<h4 id="小结">小结</h4>
<p>在<strong>这个函数中会为新的 <code>_defer</code> 设置一些基础属性，并将调用函数的参数集传入。最后通过特殊的返回方法结束函数调用</strong>。另外这一块与先前 <a href="https://segmentfault.com/a/1190000019251478#articleHeader9">《深入理解 Go panic and recover》</a> 的处理逻辑有一定关联性，其实就是 <code>gp.sched.ret</code> 返回 0 还是 1 会分流至不同处理方式</p>
<h3 id="newdefer">newdefer</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">newdefer</span>(<span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">int32</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span> {
	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
	<span style="color:#a6e22e">sc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">deferclass</span>(uintptr(<span style="color:#a6e22e">siz</span>))
	<span style="color:#a6e22e">gp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sc</span> &lt; uintptr(len(<span style="color:#a6e22e">p</span>{}.<span style="color:#a6e22e">deferpool</span>)) {
		<span style="color:#a6e22e">pp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">ptr</span>()
		<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
			<span style="color:#f92672">...</span>
			<span style="color:#a6e22e">lock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
			<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]
			<span style="color:#a6e22e">unlock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
		}
		<span style="color:#f92672">...</span>
	}
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">systemstack</span>(<span style="color:#66d9ef">func</span>() {
			<span style="color:#a6e22e">total</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">roundupsize</span>(<span style="color:#a6e22e">totaldefersize</span>(uintptr(<span style="color:#a6e22e">siz</span>)))
			<span style="color:#a6e22e">d</span> = (<span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>)(<span style="color:#a6e22e">mallocgc</span>(<span style="color:#a6e22e">total</span>, <span style="color:#a6e22e">deferType</span>, <span style="color:#66d9ef">true</span>))
		})
		<span style="color:#f92672">...</span>
	}
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span> = <span style="color:#a6e22e">siz</span>
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span> = <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span>
	<span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span> = <span style="color:#a6e22e">d</span>
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">d</span>
}
</code></pre></div><ul>
<li>从池中获取可以使用的 <code>_defer</code>，则复用作为新的基础单元</li>
<li>若在池中没有获取到可用的，则调用 <code>mallocgc</code> 重新申请一个新的</li>
<li>设置 <code>defer</code> 的基础属性，最后修改当前 <code>Goroutine</code> 的 <code>_defer</code> 指向</li>
</ul>
<p>通过这个方法我们可以注意到两点，如下：</p>
<ul>
<li><code>defer</code> 与 <code>Goroutine(g)</code> 有直接关系，所以讨论 <code>defer</code> 时基本离不开 <code>g</code> 的关联</li>
<li>新的 <code>defer</code> 总是会在现有的链表中的最前面，也就是 <code>defer</code> 的特性后进先出</li>
</ul>
<h4 id="小结-1">小结</h4>
<p>这个函数主要承担了获取新的 <code>_defer</code> 的作用，它有可能是从 <code>deferpool</code> 中获取的，也有可能是重新申请的</p>
<h3 id="deferreturn">deferreturn</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferreturn</span>(<span style="color:#a6e22e">arg0</span> <span style="color:#66d9ef">uintptr</span>) {
	<span style="color:#a6e22e">gp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>()
	<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span>
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#66d9ef">return</span>
	}
	<span style="color:#a6e22e">sp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallersp</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sp</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">sp</span> {
		<span style="color:#66d9ef">return</span>
	}

	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span> {
	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
		<span style="color:#75715e">// Do nothing.
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">sys</span>.<span style="color:#a6e22e">PtrSize</span>:
		<span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>)) = <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>))
	<span style="color:#66d9ef">default</span>:
		<span style="color:#a6e22e">memmove</span>(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>), <span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>), uintptr(<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span>))
	}
	<span style="color:#a6e22e">fn</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span>
	<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span> = <span style="color:#66d9ef">nil</span>
	<span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span> = <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span>
	<span style="color:#a6e22e">freedefer</span>(<span style="color:#a6e22e">d</span>)
	<span style="color:#a6e22e">jmpdefer</span>(<span style="color:#a6e22e">fn</span>, uintptr(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>)))
}
</code></pre></div><p>如果在一个方法中调用过 <code>defer</code> 关键字，那么编译器将会在结尾处插入 <code>deferreturn</code> 方法的调用。而该方法中主要做了如下事项：</p>
<ul>
<li>清空当前节点 <code>_defer</code> 被调用的函数调用信息</li>
<li>释放当前节点的 <code>_defer</code> 的存储信息并放回池中（便于复用）</li>
<li>跳转到调用 <code>defer</code> 关键字的调用函数处</li>
</ul>
<p>在这段代码中，跳转方法 <code>jmpdefer</code> 格外重要。因为它显式的控制了流转，代码如下：</p>
<pre><code>// asm_amd64.s
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
	MOVQ	fv+0(FP), DX	// fn
	MOVQ	argp+8(FP), BX	// caller sp
	LEAQ	-8(BX), SP	// caller sp after CALL
	MOVQ	-8(SP), BP	// restore BP as if deferreturn returned (harmless if framepointers not in use)
	SUBQ	$5, (SP)	// return to CALL again
	MOVQ	0(DX), BX
	JMP	BX	// but first run the deferred function
</code></pre><p>通过源码的分析，我们发现它做了两个很 “奇怪” 又很重要的事，如下：</p>
<ul>
<li>MOVQ -8(SP), BP：<code>-8(BX)</code> 这个位置保存的是 <code>deferreturn</code> 执行完毕后的地址</li>
<li>SUBQ $5, (SP)：<code>SP</code> 的地址减 5 ，其减掉的长度就恰好是 <code>runtime.deferreturn</code> 的长度</li>
</ul>
<p>你可能会问，为什么是 5？好吧。翻了半天最后看了一下汇编代码&hellip;嗯，相减的确是 5 没毛病，如下：</p>
<pre><code>	0x007a 00122 (main.go:7)	CALL	runtime.deferreturn(SB)
	0x007f 00127 (main.go:7)	MOVQ	56(SP), BP
</code></pre><p>我们整理一下思绪，照上述逻辑的话，那 <code>deferreturn</code> 就是一个 “递归” 了哦。每次都会重新回到 <code>deferreturn</code> 函数，那它在什么时候才会结束呢，如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferreturn</span>(<span style="color:#a6e22e">arg0</span> <span style="color:#66d9ef">uintptr</span>) {
	<span style="color:#a6e22e">gp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>()
	<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span>
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#66d9ef">return</span>
	}
	<span style="color:#f92672">...</span>
}
</code></pre></div><p>也就是会不断地进入 <code>deferreturn</code> 函数，判断链表中是否还存着 <code>_defer</code>。若已经不存在了，则返回，结束掉它。简单来讲，就是处理完全部 <code>defer</code> 才允许你真的离开它。果真如此吗？我们再看看上面的汇编代码，如下：</p>
<pre><code>    。..
	0x0070 00112 (main.go:6)	CALL	runtime.deferproc(SB)
	0x0075 00117 (main.go:6)	TESTL	AX, AX
	0x0077 00119 (main.go:6)	JNE	137
	0x0079 00121 (main.go:7)	XCHGL	AX, AX
	0x007a 00122 (main.go:7)	CALL	runtime.deferreturn(SB)
	0x007f 00127 (main.go:7)	MOVQ	56(SP), BP
	0x0084 00132 (main.go:7)	ADDQ	$64, SP
	0x0088 00136 (main.go:7)	RET
	0x0089 00137 (main.go:6)	XCHGL	AX, AX
	0x008a 00138 (main.go:6)	CALL	runtime.deferreturn(SB)
	...
</code></pre><p>的确如上述流程所分析一致，验证完毕</p>
<h4 id="小结-2">小结</h4>
<p>这个函数主要承担了清空已使用的 <code>defer</code> 和跳转到调用 <code>defer</code> 关键字的函数处，非常重要</p>
<h2 id="总结">总结</h2>
<p>我们有提到 <code>defer</code> 关键字涉及两个核心的函数，分别是 <code>deferproc</code> 和 <code>deferreturn</code> 函数。而 <code>deferreturn</code> 函数比较特殊，是当应用函数调用 <code>defer</code> 关键字时，编译器会在其结尾处插入 <code>deferreturn</code> 的调用，它们俩一般都是成对出现的</p>
<p>但是当一个 <code>Goroutine</code> 上存在着多次 <code>defer</code> 行为（也就是多个 <code>_defer</code>）时，编译器会进行利用一些小技巧， 重新回到 <code>deferreturn</code> 函数去消耗 <code>_defer</code> 链表，直到一个不剩才允许真正的结束</p>
<p>而新增的基础单元 <code>_defer</code>，有可能是被复用的，也有可能是全新申请的。它最后都会被追加到 <code>_defer</code> 链表的表头，从而设定了后进先出的调用特性</p>
<h2 id="关联">关联</h2>
<ul>
<li><a href="https://github.com/EDDYCJY/blog/blob/master/golang/pkg/2019-05-18-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go-panic-and-recover.md">深入理解 Go panic and recover</a></li>
</ul>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html">Scheduling In Go</a></li>
<li><a href="http://hustcat.github.io/dive-into-stack-defer-panic-recover-in-go/">Dive into stack and defer/panic/recover in go</a></li>
<li><a href="https://github.com/cch123/golang-notes/blob/master/defer.md">golang-notes</a></li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/panic/2019-05-21-panic-and-recover/">深入理解 Go panic and recover</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/map/2019-04-07-why-map-no-order/">为什么遍历 Go map 是无序的</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/map/2019-03-24-map-assign/">深入理解 Go map：赋值和扩容迁移</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/map/2019-03-05-map-access/">深入理解 Go map：初始化和访问元素</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/slice/2019-01-06-why-slice-max/">Go Slice 最大容量大小是怎么来的</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            <li>
                                                <a href="/tags/%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" title="View all posts tagged match">源码分析</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.site/posts/go/panic/2019-05-21-panic-and-recover/"
                                                    rel="prev">深入理解 Go panic and recover</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.site/posts/go/talk/2019-06-16-defer-loss/">Go1.12 defer 会有性能损耗，尽量不要用？</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.site/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.vip All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>